package com.k2data.k2app.service;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.k2data.k2app.common.K2Assert;
import com.k2data.k2app.common.OrikaMapperFactory;
import com.k2data.k2app.constant.Constant;
import com.k2data.k2app.domain.*;
import com.k2data.k2app.domain.po.*;
import com.k2data.k2app.exception.K2ResponseException;
import com.k2data.k2app.mapper.*;
import com.k2data.k2app.rest.InstanceClient;
import com.k2data.k2app.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.nio.file.Paths;
import java.util.List;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * @author lidong9144@163.com 17-4-5.
 */
@Service
public class TemplateService {

    private final InstanceClient instanceClient;

    private final TDeviceMapper tDeviceMapper;
    private final TMonitorLimitValueMapper tMonitorLimitValueMapper;
    private final TMonitorPointMapper tMonitorPointMapper;
    private final TMonitorVariableMapper tMonitorVariableMapper;
    private final TDeviceBomMapper tDeviceBomMapper;
    private final TRDeviceMonitorPointMapper trDeviceMonitorPointMapper;
    private final DeviceMapper deviceMapper;
    private final CustomerMapper customerMapper;

    @Autowired
    public TemplateService(TDeviceMapper tDeviceMapper, TMonitorLimitValueMapper tMonitorLimitValueMapper,
                           TMonitorPointMapper tMonitorPointMapper, TMonitorVariableMapper tMonitorVariableMapper,
                           TDeviceBomMapper tDeviceBomMapper, TRDeviceMonitorPointMapper trDeviceMonitorPointMapper,
                           InstanceClient instanceClient,DeviceMapper deviceMapper,CustomerMapper customerMapper) {
        this.tDeviceMapper = tDeviceMapper;
        this.tMonitorLimitValueMapper = tMonitorLimitValueMapper;
        this.tMonitorPointMapper = tMonitorPointMapper;
        this.tMonitorVariableMapper = tMonitorVariableMapper;
        this.tDeviceBomMapper = tDeviceBomMapper;
        this.trDeviceMonitorPointMapper = trDeviceMonitorPointMapper;
        this.instanceClient = instanceClient;
        this.deviceMapper=deviceMapper;
        this.customerMapper=customerMapper;
    }

    @Transactional
    public Long insertDevice(TDevice device) throws K2ResponseException {
        if (device.getParentId() == null || device.getParentId() == 0L) {
            device.setIsRoot(Constant.YES);
        } else {
            K2Assert.existTemplateDeviceId(device.getParentId());
        }

        tDeviceMapper.insertSelective(device);

        // bom 关联表
        TDeviceBom bom = new TDeviceBom();
        bom.setDeviceId(device.getId());
        bom.setParentId(device.getParentId() == null ? 0L : device.getParentId());
        tDeviceBomMapper.insertSelective(bom);

        if (device.getMonitorPointIds() != null && !device.getMonitorPointIds().isEmpty()) {
            Long rootId = selectRootDeviceId(device.getId());
            for (Long pid : device.getMonitorPointIds()) {
                TRDeviceMonitorPoint po = new TRDeviceMonitorPoint();
                po.setDeviceId(device.getId());
                po.setMonitorPointId(pid);
                po.setRootId(rootId);
                trDeviceMonitorPointMapper.insertSelective(po);
            }
        }

        return device.getId();
    }

    @Transactional
    public Long updateDevice(TDevice device) throws K2ResponseException {
        if (device.getParentId() != null) {
            K2Assert.existRelationTemplateDeviceId(device.getParentId());

            TDeviceBom bom = new TDeviceBom();
            bom.setDeviceId(device.getId());
            bom.setParentId(device.getParentId());
            tDeviceBomMapper.updateByDeviceId(bom);

            if (device.getParentId() == 0L) {
                device.setIsRoot(Constant.YES);
            } else {
                device.setIsRoot(Constant.NO);
            }
        }

        tDeviceMapper.updateByPrimaryKeySelective(device);

        return device.getId();
    }

    @Transactional
    public Long deleteDevice(Long id) throws InterruptedException {
        // 逻辑删除
        TDevice tDevice = new TDevice();
        tDevice.setId(id);
        tDevice.setIsDelete(Constant.YES);
        tDeviceMapper.updateByPrimaryKeySelective(tDevice);

        return id;
    }

    @Transactional
    public Long insertDevicePoint(Long id, Long pointId) throws K2ResponseException {
        TRDeviceMonitorPoint r = new TRDeviceMonitorPoint();
        r.setDeviceId(id);
        r.setMonitorPointId(pointId);
//        r.setNum(trDeviceMonitorPointMapper.selectMaxNum(id, pointId) + 1);
        trDeviceMonitorPointMapper.insertSelective(r);

        return 1L;
    }

    @Transactional
    public Integer deleteDevicePoints(Long id, Long rid) {
        TRDeviceMonitorPoint cond = new TRDeviceMonitorPoint();
        cond.setDeviceId(id);
        cond.setId(rid);
        trDeviceMonitorPointMapper.delete(cond);
        return 1;
    }

    @Transactional
    public Integer insertDevicePoints(DevicePointDTO points) throws InterruptedException, K2ResponseException {
        Set<Long> components = Sets.newHashSet();
        List<BomDTO> boms = selectTDeviceBomWide(points.getRootId());
        for (BomDTO bom : boms) {
            components.add(bom.getId());
        }

        if (points.getPoints() == null) {
            return 1;
        }

        for (TRDeviceMonitorPointDTO point : points.getPoints()) {
            if (!components.contains(point.getDeviceId())) {
                throw new K2ResponseException(400000, String.format("Device is not a root's component. deviceId: %s, rootId: %s",
                        point.getDeviceId(),
                        points.getRootId()));
            }

            TRDeviceMonitorPoint po = OrikaMapperFactory.getMapper().map(point, TRDeviceMonitorPoint.class);
            if (point.getId() != null) {
                trDeviceMonitorPointMapper.updateByPrimaryKeySelective(po);
            } else {
                po.setRootId(points.getRootId());
                trDeviceMonitorPointMapper.insertSelective(po);
            }
        }

        return 1;
    }

    @Transactional
    public Long deleteDevicePoint(Long rid) {
        TRDeviceMonitorPoint cond = new TRDeviceMonitorPoint();
        cond.setId(rid);
        trDeviceMonitorPointMapper.delete(cond);
        return rid;
    }

    @Transactional
    public Long insertMonitorLimitValue(TMonitorLimitValue po) throws K2ResponseException {
//        if (!"le".equals(po.getRule())
//                && !"gt".equals(po.getRule())
//                && !"gle".equals(po.getRule())
//                && !"ggt".equals(po.getRule())) {
//            throw new K2ResponseException(400000, "Rule must be one of (le, gt, ggt, gle)");
//        }
//
//        switch (po.getRule()) {
//            case "le":
//                po.setLevel(1);
//                break;
//            case "gt":
//                po.setLevel(2);
//                break;
//            case "gle":
//                po.setLevel(3);
//                break;
//            case "ggt":
//                po.setLevel(4);
//                break;
//            default:
//                break;
//        }

        boolean flag = !"lt".equals(po.getRule())
                && !"glt".equals(po.getRule())
                && !"gt".equals(po.getRule())
                && !"ggt".equals(po.getRule());
        if (flag) {
            throw new K2ResponseException(400000, "Rule must be one of (lt, glt, gt, ggt)");
        }
        String rule = po.getRule();
        switch (rule) {
            case "lt":
                po.setLevel(1);
                break;
            case "glt":
                po.setLevel(2);
                break;
            case "gt":
                po.setLevel(1);
                break;
            case "ggt":
                po.setLevel(2);
                break;
            default:
                break;
        }

        tMonitorLimitValueMapper.insertSelective(po);
        return po.getId();
    }

    @Transactional
    public Long updateMonitorLimitValue(TMonitorLimitValue po) {
        tMonitorLimitValueMapper.updateByPrimaryKeySelective(po);
        return po.getId();
    }

    @Transactional
    public Long deleteMonitorLimitValue(TMonitorLimitValue po) {
        tMonitorLimitValueMapper.delete(po);

        return po.getId();
    }

    @Transactional
    public Long insertMonitorPoint(TMonitorPoint po) throws K2ResponseException {
        K2Assert.checkSensorNameEn(po.getNameEn());

        tMonitorPointMapper.insertSelective(po);

        return po.getId();
    }

    @Transactional
    public Long updateMonitorPoint(TMonitorPoint po) throws K2ResponseException {
        K2Assert.checkSensorNameEn(po.getNameEn());

        tMonitorPointMapper.updateByPrimaryKeySelective(po);
        return po.getId();
    }

    @Transactional
    public Long deleteMonitorPoint(TMonitorPoint po) {
        // 删除阈值
        TMonitorVariable varCond = new TMonitorVariable();
        varCond.setMonitorPointId(po.getId());
        for (TMonitorVariable tMonitorVariable : tMonitorVariableMapper.select(varCond)) {
            TMonitorLimitValue limitCond = new TMonitorLimitValue();
            limitCond.setVariableId(tMonitorVariable.getId());
            tMonitorLimitValueMapper.delete(limitCond);
        }

        // 删除变量
        tMonitorVariableMapper.delete(varCond);

        // 删除主表
        tMonitorPointMapper.delete(po);

        // 删除 device 关联表
        TRDeviceMonitorPoint rCond = new TRDeviceMonitorPoint();
        rCond.setMonitorPointId(po.getId());
        trDeviceMonitorPointMapper.delete(rCond);

        return po.getId();
    }

    @Transactional
    public Long insertMonitorValue(TMonitorVariable po) throws K2ResponseException {
        TMonitorVariable checkCond = new TMonitorVariable();
        checkCond.setMonitorPointId(po.getMonitorPointId());
        checkCond.setKmxVariableNameEn(po.getKmxVariableNameEn());

        if (tMonitorVariableMapper.checkExist(checkCond) != null) {
            throw new K2ResponseException(400000, String.format("PointId: %s, nameEn: %s already exist.", po.getMonitorPointId(), po.getKmxVariableNameEn()));
        }
/*        if(po.getType()==2){
            List<TMonitorVariable> tList=tMonitorVariableMapper.selectTypeNumByMPId(po.getMonitorPointId());
            if(tList.size()>=3){
                throw new K2ResponseException(400000, String.format("关键性变量个数不能超过3个！变量名称: %s ,%s ,%s 。",
                        tList.get(0).getKmxVariableName(), tList.get(1).getKmxVariableName(),tList.get(2).getKmxVariableName()));
            }else{
                tMonitorVariableMapper.insertSelective(po);
            }
        }else{
            tMonitorVariableMapper.insertSelective(po);
        }*/
        tMonitorVariableMapper.insertSelective(po);

        return po.getId();
    }

    @Transactional
    public Long updateMonitorValue(TMonitorVariable po) throws K2ResponseException{
       /* TMonitorVariable t=tMonitorVariableMapper.selectByPrimaryKey(po.getId());
        if(t.getType()!=2&&po.getType()==2){
            List<TMonitorVariable> tList=tMonitorVariableMapper.selectTypeNumByMPId(po.getMonitorPointId());
            if(tList.size()>=3){
                throw new K2ResponseException(400000, String.format("关键性变量个数不能超过3个！变量名称: %s ,%s ,%s 。",
                        tList.get(0).getKmxVariableName(), tList.get(1).getKmxVariableName(),tList.get(2).getKmxVariableName()));
            }else{
                tMonitorVariableMapper.updateByPrimaryKeySelective(po);
            }
        }else{
            tMonitorVariableMapper.updateByPrimaryKeySelective(po);
        }*/
        tMonitorVariableMapper.updateByPrimaryKeySelective(po);
        return po.getId();
    }

    @Transactional
    public Long deleteMonitorValue(TMonitorVariable po) {
        TMonitorLimitValue limitCond = new TMonitorLimitValue();
        limitCond.setVariableId(po.getId());
        tMonitorLimitValueMapper.delete(limitCond);

        tMonitorVariableMapper.delete(po);

        return po.getId();
    }

    public List<MonitorDTO> selectTDevicePoints(DeviceCond cond) {
        List<MonitorDTO> list = Lists.newArrayList();
        List<TMonitorPoint> source = Lists.newArrayList();
        if(cond.getCustomerId() != null){
            source = tMonitorPointMapper.selectPointsByCustomer(cond);
            cond.setDeviceId(null);
        }else{
            source = tMonitorPointMapper.selectPoints(cond);
        }

        for (TMonitorPoint po : source) {
            if (cond.getDeviceId() != null || cond.getRootId() != null) {
                for (TRDeviceMonitorPoint trDeviceMonitorPoint : po.getRs()) {
                    MonitorDTO dto = new MonitorDTO();
                    dto.setId(po.getId());
                    dto.setSensorName(po.getSensorName());
                    dto.setSensorType(po.getSensorType());
                    dto.setNameEn(po.getNameEn());
                    dto.setDeviceName(trDeviceMonitorPoint.getDeviceName());
                    dto.setState(po.getState());
                    dto.setRid(trDeviceMonitorPoint.getId());
                    list.add(dto);
                }
            } else {
                MonitorDTO dto = new MonitorDTO();
                dto.setId(po.getId());
                dto.setSensorName(po.getSensorName());
                dto.setSensorType(po.getSensorType());
                dto.setNameEn(po.getNameEn());
                dto.setDeviceName(po.getDeviceName());
                dto.setState(po.getState());
                list.add(dto);
            }
        }

        for (MonitorDTO monitorDTO:list) {
            if(monitorDTO.getSensorType().contains("tcs")){
                String[] arr =monitorDTO.getSensorType().split("_");
                String deviceNo=arr[1]+"_10000_"+arr[2];
                String customerNo=arr[0]+"_"+arr[1];
                Device device=deviceMapper.selectByDeviceNo(deviceNo);
                Customer customer=customerMapper.selectByCustomerNo(customerNo);
                monitorDTO.setSensorName(device.getName());//设置成设备名字
                monitorDTO.setSensorType(customer.getName());//设置成客户名称
            }
        }
        return list;
    }

    /**
     * 取出设备下所有测点
     */
    public List<MonitorDTO> selectAllVariables(DeviceCond cond) throws InterruptedException {
        List<MonitorDTO> list = Lists.newArrayList();

        List<BomDTO> bom = selectTDeviceBomWide(cond.getDeviceId());
        for (BomDTO tDeviceBomPO : bom) {
            DeviceCond innerCond = new DeviceCond();
            innerCond.setDeviceId(tDeviceBomPO.getId());

            list.addAll(selectTDeviceVariables(innerCond));
        }

        return list;
    }

    public List<MonitorDTO> selectTDeviceVariables(DeviceCond cond) {
        List<MonitorDTO> list = Lists.newArrayList();

        for (TMonitorVariable po : tMonitorVariableMapper.selectVariables(cond)) {
            MonitorDTO dto = OrikaMapperFactory.getMapper().map(po, MonitorDTO.class);

            list.add(dto);
        }

        return list;
    }

    public List<MonitorDTO> selectTDeviceLimit(DeviceCond cond) {
        List<MonitorDTO> list = Lists.newArrayList();
        for (TMonitorLimitValue po : tMonitorLimitValueMapper.selectLimitValue(cond)) {
            MonitorDTO dto = new MonitorDTO();
            dto.setName(po.getName());
            dto.setValue(po.getValue());
            dto.setRule(po.getRule());
            dto.setId(po.getId());
            dto.setVariableId(po.getVariableId());
            dto.setState(po.getState());

            list.add(dto);
        }

        return list;
    }

    /**
     * 遍历 bom 树, 广度优先
     * <p>把所有 bom 存入 list
     */
    public List<BomDTO> selectTDeviceBomWide(Long tDeviceId) throws InterruptedException {
        LinkedBlockingQueue<Long> deviceIdQueue = new LinkedBlockingQueue<>();

        List<BomDTO> boms = Lists.newArrayList();

        TDeviceBom condSelf = new TDeviceBom();
        condSelf.setDeviceId(tDeviceId);
        TDeviceBom self = tDeviceBomMapper.selectOneBom(condSelf);
        if (self != null) {
            BomDTO dto = new BomDTO();

            if (self.getDevice() != null) {
                dto = OrikaMapperFactory.getMapper().map(self.getDevice(), BomDTO.class);

                if (!StringUtils.isBlank(self.getDevice().getImageUrl())) {
                    dto.setImageUrl(instanceClient.getGatewayUrl() + Paths.get("/static/", self.getDevice().getImageUrl()).toString());
                } else {
                    dto.setImageUrl(instanceClient.getGatewayUrl() + "/static/template_default.png");
                }
            }

            dto.setBomId(self.getId());
            dto.setParentId(self.getParentId());

            boms.add(dto);
        }

        TDeviceBom cond = new TDeviceBom();
        cond.setParentId(tDeviceId);
        addWideItem(cond, deviceIdQueue, boms);

        while (!deviceIdQueue.isEmpty()) {
            TDeviceBom innerCond = new TDeviceBom();
            innerCond.setParentId(deviceIdQueue.take());

            addWideItem(innerCond, deviceIdQueue, boms);
        }

        return boms;
    }

    private void addWideItem(TDeviceBom innerCond, LinkedBlockingQueue<Long> deviceIdQueue, List<BomDTO> boms) throws InterruptedException {
        List<TDeviceBom> leafs = tDeviceBomMapper.selectBom(innerCond);

        for (TDeviceBom leaf : leafs) {
            deviceIdQueue.put(leaf.getDeviceId());

            BomDTO dto = new BomDTO();
            if (leaf.getDevice() != null) {
                dto = OrikaMapperFactory.getMapper().map(leaf.getDevice(), BomDTO.class);

                if (Constant.YES.equals(leaf.getDevice().getIsRoot())) {
                    if (!StringUtils.isBlank(leaf.getDevice().getImageUrl())) {
                        dto.setImageUrl(instanceClient.getGatewayUrl() + Paths.get("/static/", leaf.getDevice().getImageUrl()).toString());
                    } else {
                        dto.setImageUrl(instanceClient.getGatewayUrl() + "/static/template_default.png");
                    }
                }
            }
            dto.setBomId(leaf.getId());
            dto.setParentId(leaf.getParentId());
            boms.add(dto);
        }
    }

    /**
     * 遍历 bom 树, 递归, 深度优先
     */
    public List<BomDTO> selectTDeviceBomDeep(TDevice cond) throws InterruptedException {
        List<BomDTO> bom = Lists.newArrayList();

        bom(cond, bom);

        return bom;
    }

    private void bom(TDevice cond, List<BomDTO> children) {
        TDeviceBom innerCond = new TDeviceBom();
        innerCond.setParentId(cond.getParentId());
        innerCond.setId(cond.getId());

        List<TDeviceBom> leafs = tDeviceBomMapper.selectBom(innerCond);
        for (TDeviceBom leaf : leafs) {
            BomDTO inner = new BomDTO();
            inner.setId(leaf.getDeviceId());
            inner.setParentId(leaf.getParentId() == null ? 0L : leaf.getParentId());
            inner.setName(leaf.getDevice().getName());
            inner.setType(leaf.getDevice().getType());
            inner.setSpec(leaf.getDevice().getSpec());
            inner.setChildren(Lists.newArrayList());
            if (Constant.YES.equals(leaf.getDevice().getIsRoot())) {
                if (!StringUtils.isBlank(leaf.getDevice().getImageUrl())) {
                    inner.setImageUrl(instanceClient.getGatewayUrl() + Paths.get("/static/", leaf.getDevice().getImageUrl()).toString());
                } else {
                    inner.setImageUrl(instanceClient.getGatewayUrl() + "/static/template_default.png");
                }
            }
            children.add(inner);

            TDevice tCond = new TDevice();
            tCond.setParentId(inner.getId());

            bom(tCond, inner.getChildren());
        }
    }

    /**
     * 找到叶子的根节点 id
     */
    public Long selectRootDeviceId(Long leafId) {
        TDeviceBom cond = new TDeviceBom();
        cond.setDeviceId(leafId);
        TDeviceBom r = tDeviceBomMapper.selectOne(cond);

        if (new Long(0L).equals(r.getParentId())) {
            return r.getDeviceId();
        }

        return selectRootDeviceId(r.getParentId());
    }

    /**
     *
     * @return
     */
    @Transactional
    public Long updateDeviceType(Long tVariableId){
        tMonitorVariableMapper.updateDeviceType(tVariableId);
        return 1L;
    }
}
